﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace CardPlay.Model.CardModel
{
    public class DealScript
    {
        public static readonly Suit[] SuitSequence = new[] { Suit.Spades, Suit.Clubs, Suit.Hearts, Suit.Diamonds };

        private readonly List<DealInstruction> _script = new List<DealInstruction>();

        public class DealInstruction
        {
            private string _instruction = DealExpression.Up;
            private string _nameOfTarget;

            internal DealInstruction(DealScript container)
            {
                Script = container;
                Script._script.Add(this);  
            }

            public DealInstruction Deal(string instruction)
            {
                Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(instruction));

                _instruction = instruction;
                return this;
            }

            public DealInstruction To(string nameOfTarget)
            {
                Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(nameOfTarget));

                _nameOfTarget = nameOfTarget;
                return this;
            }

            public IDealAction ToAction()
            {
                if (HasMultipleSteps) return new DealSequence(_nameOfTarget, _instruction);

                var action = String.IsNullOrEmpty(_instruction) ? DealCardAction.Default : DealCardAction.Parse(_instruction);
                action.Target = _nameOfTarget;

                return action;
            }

            private DealScript Script
            {
                get; set;
            }

            private bool HasMultipleSteps
            {
                get { return _instruction.IndexOfAny(DealSequence.Delimiters) >= 0; }
            }
        }

        public void DealCardTo(string location)
        {
            To(location);
        }

        public void DealRemainingFaceUpTo(string location)
        {
            DealRemaining(CardOrientation.FaceUp).To(location);
        }

        public void DealRemainingFaceDownTo(string location)
        {
            DealRemaining(CardOrientation.FaceDown).To(location);
        }

        public DealInstruction To(string location)
        {
            return new DealInstruction(this).To(location);
        }

        public DealInstruction Deal(string instruction)
        {
            return new DealInstruction(this).Deal(instruction);
        }

        public void DealAcesTo(IEnumerable<string> namesOfPiles, CardOrientation orientation = CardOrientation.FaceUp)
        {
            DealRankTo(namesOfPiles, Rank.Ace, orientation);
        }

        public void DealRankTo(IEnumerable<string> namesOfPiles, Rank rank, CardOrientation orientation = CardOrientation.FaceUp)
        {
            var i = 0;

            foreach (var pileName in namesOfPiles)
            {
                Deal(DealExpression.ForCard(SuitSequence[i], rank, orientation)).To(pileName);
                i = (i + 1) % SuitSequence.Length;
            }
        }

        public void DealToEach(IEnumerable<string> locations, string instruction)
        {
            foreach (var location in locations) Deal(instruction).To(location);
        }

        public void DealCardToEach(IEnumerable<string> locations)
        {
            foreach (var location in locations) DealCardTo(location);
        }

        public void DealCardToEach(params string[] locations)
        {
            foreach (var location in locations) DealCardTo(location);
        }

        public IDealAction Compile()
        {
            return new DealSequence(_script.Select(instructions => instructions.ToAction()));
        }

        private DealInstruction DealRemaining(CardOrientation orientation)
        {
            return Deal(DealExpression.ForRemainingCards(orientation));
        }
    }
}
